/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.ranch.api.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.ranch.api.ConceptCategoryAnnotation;
import org.ranch.api.RanchConstants;
import org.ranch.api.service.ConceptService;
import org.ranch.api.utils.RanchStringUtil;
import org.ranch.dao.ConceptCategoryDAO;
import org.ranch.dao.ConceptDAO;
import org.ranch.model.Concept;
import org.ranch.model.ConceptCategory;
import org.ranch.model.RecordStatus;
import org.ranch.model.exception.RanchValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.googlecode.genericdao.search.Filter;
import com.googlecode.genericdao.search.Search;

/**
 * Implementation of {@link ConceptService}
 */
@Transactional
@Service("conceptService")
public class ConceptServiceImpl implements ConceptService{

    @Autowired
    private ConceptDAO conceptDAO;

    @Autowired
    private ConceptCategoryDAO conceptCategoryDAO;

    /**
     * default constructor
     */
    public ConceptServiceImpl() {
	super();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#saveConcept
     * (org.mak.cis.mohr.model.Concept)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Concept saveConcept(Concept concept) throws RanchValidationException {
	validateConcept(concept);
	concept.setName(RanchStringUtil.convertToTitleCase(concept.getName()));

	String errorMsg = "A concept with the same name already exists.";
	if(StringUtils.isBlank(concept.getId())){
	    if(conceptDAO.exists(concept.getName()))
		throw new RanchValidationException(errorMsg);
	} else {
	    String existingId = conceptDAO.getConceptIdByName(concept.getName());
	    if (StringUtils.isNotBlank(existingId) && !concept.getId().equalsIgnoreCase(existingId)) {
		throw new RanchValidationException(errorMsg);
	    }
	}
	
	conceptDAO.save(concept);
	return concept;
    }

    private void validateConcept(Concept concept) throws RanchValidationException {
	if (concept == null) {
	    throw new RanchValidationException("supplied concept cannot be null");
	}

	if (StringUtils.isBlank(concept.getName()) || StringUtils.isEmpty(concept.getName())) {
	    throw new RanchValidationException("supplied concept name cannot be null");
	}
	
	if (concept.getCategories() == null || concept.getCategories().size() <= 0) {
	    throw new RanchValidationException("supplied concept has not categories");
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#deleteConcept
     * (org.mak.cis.mohr.model.Concept)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteConcept(Concept concept) {
	conceptDAO.delete(concept);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#getConceptById
     * (java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public Concept getConceptById(String id) {
	return conceptDAO.searchUniqueByPropertyEqual("id", id);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#getConceptByCode
     * (java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public Concept getConceptByCode(String code) {
	return conceptDAO.searchUniqueByPropertyEqual("code", code);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * getConceptsByCategory(org.mak.cis.mohr.model.ConceptCategory)
     */
    @Override
    @Transactional(readOnly = true)
    public List<Concept> getConceptsByCategory(ConceptCategory conceptCategory) {
	Search search = new Search();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	search.addFilter(Filter.some("categories", Filter.equal("id", conceptCategory.getId())));
	List<Concept> concepts = conceptDAO.search(search);
	Collections.sort(concepts);
	return concepts;
    }

    @Override
    public List<Concept> getConceptsByCategory(ConceptCategory conceptCategory, int pageNo) {

	Search search = new Search();
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);
	search.addSort("name", false, true);
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	search.addFilter(Filter.some("categories", Filter.equal("id", conceptCategory.getId())));

	/*
	 * if the page number is less than or equal to zero, no need for paging
	 */
	if (pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}

	List<Concept> concepts = conceptDAO.search(search);
	Collections.sort(concepts);
	return concepts;
    }

    @Override
    public List<Concept> getConceptsByCategoryAnnotation(
	    ConceptCategoryAnnotation conceptCategoryAnnotation) throws SecurityException,
	    NoSuchFieldException {
	ConceptCategory conceptCategoryNationality = getConceptCategoryById(conceptCategoryAnnotation
		.id());

	List<Concept> concepts = getConceptsByCategory(conceptCategoryNationality);

	return concepts;
    }

    @Override
    public int getNumberOfConceptsByCategory(ConceptCategory conceptCategory) {
	Search search = new Search();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	search.addFilter(Filter.some("categories", Filter.equal("id", conceptCategory.getId())));

	return conceptDAO.count(search);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#getAllConcepts ()
     */
    @Override
    @Transactional(readOnly = true)
    public List<Concept> getAllConcepts() {
	List<Concept> concepts = conceptDAO.searchByRecordStatus(RecordStatus.ACTIVE);
	Collections.sort(concepts);
	return concepts;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#getConceptByPage(java.lang
     * .Integer)
     */
    @Override
    @Transactional(readOnly = true)
    public List<Concept> getConceptByPage(Integer pageNo) {
	Search search = new Search();
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);
	search.addSort("name", false, true);
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);

	/*
	 * if the page number is less than or equal to zero, no need for paging
	 */
	if (pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}

	List<Concept> concepts = conceptDAO.search(search);
	Collections.sort(concepts);
	return concepts;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * saveConceptCategory(org.mak.cis.mohr.model.ConceptCategory)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public ConceptCategory saveConceptCategory(ConceptCategory conceptCategory)
	    throws RanchValidationException {
	if (conceptCategory == null) {
	    throw new RanchValidationException("supplied concept category cannot be null");
	}

	if (StringUtils.isBlank(conceptCategory.getName())
		|| StringUtils.isEmpty(conceptCategory.getName())) {
	    throw new RanchValidationException(
		    "name of supplied concept category cannot be blank or empty");
	}

	/*
	 * if (StringUtils.isBlank(conceptCategory.getCode()) ||
	 * StringUtils.isEmpty(conceptCategory.getCode())) { throw new
	 * RanchValidationException(
	 * "code of supplied concept category cannot be blank or empty"); }
	 */

	conceptCategory.setName(RanchStringUtil.capitalizeFirstLettersTokenizer(conceptCategory
		.getName()));
	conceptCategoryDAO.save(conceptCategory);
	return conceptCategory;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * deleteConceptCategory(org.mak.cis.mohr.model.ConceptCategory)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteConceptCategory(ConceptCategory conceptCategory) {
	conceptCategoryDAO.delete(conceptCategory);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * getConceptCategoryById(java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public ConceptCategory getConceptCategoryById(String id) {
	return conceptCategoryDAO.find(id);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * getConceptCategoryByCode(java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public ConceptCategory getConceptCategoryByCode(String code) {
	return conceptCategoryDAO.searchUniqueByPropertyEqual("code", code);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * getAllConceptCategories()
     */
    @Override
    @Transactional(readOnly = true)
    public List<ConceptCategory> getAllConceptCategories() {
	List<ConceptCategory> categories = conceptCategoryDAO
		.searchByRecordStatus(RecordStatus.ACTIVE);
	Collections.sort(categories);
	return categories;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#purgeConcept
     * (org.mak.cis.mohr.model.Concept)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void purgeConcept(Concept concept) {
	// TODO perform business logic before removing concept from system
	// TODO make sure system defined concepts are not removed
	conceptDAO.remove(concept);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#
     * purgeConceptCategory(org.mak.cis.mohr.model.ConceptCategory)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void purgeConceptCategory(ConceptCategory conceptCategory) {
	// TODO perform business logic before removing concept from system
	conceptCategoryDAO.remove(conceptCategory);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mak.cis.mohr.api.service.ConceptService#getNumberOfConcepts()
     */
    @Override
    public int getNumberOfConcepts() {
	Search search = new Search();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	return conceptDAO.count(search);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#getConceptCategoryByPage(
     * java.lang.Integer)
     */
    @Override
    @Transactional(readOnly = true)
    public List<ConceptCategory> getConceptCategoryByPage(Integer pageNo) {
	Search search = new Search();
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);
	search.addSort("name", false, true);
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);

	/*
	 * if the page number is less than or equal to zero, no need for paging
	 */
	if (pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}

	List<ConceptCategory> categories = conceptCategoryDAO.search(search);
	Collections.sort(categories);
	return categories;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#getNumberOfConceptCategories
     * ()
     */
    @Override
    public int getNumberOfConceptCategories() {
	Search search = new Search();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	return conceptCategoryDAO.count(search);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#searchConceptCategoriesByName
     * (java.lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public List<ConceptCategory> searchConceptCategoriesByName(String query) {
	String param = new StringBuilder().append("%").append(StringEscapeUtils.escapeSql(query))
		.append("%").toString();
	Search search = new Search();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	search.addFilterLike("name", param);

	List<ConceptCategory> categories = conceptCategoryDAO.search(search);
	Collections.sort(categories);
	return categories;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#searchConceptByName(java.
     * lang.String)
     */
    @Override
    @Transactional(readOnly = true)
    public List<Concept> searchConceptByName(String query) {
	Search search = prepareConceptSearch(query);

	List<Concept> concepts = conceptDAO.search(search);
	Collections.sort(concepts);
	return concepts;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#searchConceptByName(java.
     * lang.String, java.lang.Integer)
     */
    @Override
    @Transactional(readOnly = true)
    public List<Concept> searchConceptByName(String query, Integer pageNo) {
	Search search = prepareConceptSearch(query);
	search.setMaxResults(RanchConstants.MAX_NUM_PAGE_RECORD);

	/*
	 * if the page number is less than or equal to zero, no need for paging
	 */
	if (pageNo > 0) {
	    search.setPage(pageNo - 1);
	} else {
	    search.setPage(0);
	}

	List<Concept> concepts = conceptDAO.search(search);
	if (null == concepts)
	    return new ArrayList<Concept>();
	return concepts;
    }

    @Override
    public int getTotalNumberOfConceptsInSearch(String query, Integer pageNo) {
	Search search = prepareConceptSearch(query);
	int count = conceptDAO.count(search);
	return count;
    }

    /**
     * Prepares the search to be used in searching concepts
     * 
     * @param query
     * @return
     */
    public static Search prepareConceptSearch(String query) {

	Search search = new Search();
	String param = new StringBuilder().append("%").append(StringEscapeUtils.escapeSql(query))
		.append("%").toString();
	search.addFilterEqual("recordStatus", RecordStatus.ACTIVE);
	search.addFilterLike("name", param);
	search.addSort("name", false, true);

	return search;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mak.cis.mohr.api.service.ConceptService#deleteConcepts(java.util.
     * List)
     */
    @Override
    public void deleteConcepts(List<String> conceptIds) {
	if (conceptIds != null) {
	    for (String conceptId : conceptIds) {
		Concept concept = conceptDAO.find(conceptId);
		if (concept != null) {
		    conceptDAO.remove(concept);
		}
	    }
	}
    }

    /**
     * @return the conceptDAO
     */
    public ConceptDAO getConceptDAO() {
	return conceptDAO;
    }

    /**
     * @param conceptDAO
     *            the conceptDAO to set
     */
    public void setConceptDAO(ConceptDAO conceptDAO) {
	this.conceptDAO = conceptDAO;
    }

    /**
     * @return the conceptCategoryDAO
     */
    public ConceptCategoryDAO getConceptCategoryDAO() {
	return conceptCategoryDAO;
    }

    /**
     * @param conceptCategoryDAO
     *            the conceptCategoryDAO to set
     */
    public void setConceptCategoryDAO(ConceptCategoryDAO conceptCategoryDAO) {
	this.conceptCategoryDAO = conceptCategoryDAO;
    }

    @Override
    @Transactional(readOnly = true)
    public Concept getConceptByName(String name) {
	return conceptDAO.searchUniqueByPropertyEqual("name", name);
    }

}
